#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
文件名称: chart_api.py
完整路径: backend/api/chart_api.py
功能说明:
    图表生成接口，支持固定模板 (template1/按年龄段, template2/按性别) 和自定义查询 (query_mode="custom")模式下的图表生成。
    固定模板下：
      - template1 按年龄段分组，分组名称设置为"年龄"；
      - template2 按性别分组，分组名称设置为"性别"。
    数据查询结果中，每行第一列显示分组值，中间为各统计指标的"数量"和"占比"，最后一列为统计总数。
    合计行中，"数量"列直接累加，各"占比"列按对应数量与总体统计总数计算。
修改要点:
1. 修正合计行累加逻辑：循环范围扩展到最后一列，并对"占比"列重新计算。
2. 固定模板模式下显式设置 group_title，以确保第一列表头不为空。
3. 对于自定义查询模式，如果统计指标未选择子选项则返回错误提示。
4. 当分组字段为多选且勾选了子选项，后端需对该字段执行 col.in_(val) 过滤，仅统计用户选择的分组值。
5. 在 FIELD_DISPLAY_MAPPING 中增加 "grade": "年级"，保证第一列显示中文"年级"。
6. 针对高级条件解析中数值字段，区分单值与区间查询；支持多个区间（每个区间以字典形式传入），以及单一数值（若 dict 中只有 min 或 max），并对区间标签进行格式化（整数显示为整数，浮点数保留小数），同时在累加过程中将 None 转换为 0。
7. 扩展 METRIC_CONFIG 配置，添加 complete_fields 列表中所有字段的配置。
"""
# pylint: disable=unused-import 临时禁用警告

# 标准库
import datetime
import json
import traceback
from io import BytesIO
from itertools import combinations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from backend.models.student_extension import StudentExtension  # 静态类型检查


# 第三方库
from sqlalchemy import func, and_, case, literal, or_
from flask import Blueprint, request, jsonify, current_app
from openpyxl.styles import numbers  # 确保导入 numbers 模块
from backend.infrastructure.database import db
from openpyxl import Workbook
from openpyxl.styles import Alignment
from flask import send_file
import pandas as pd
from backend.models.student_extension import StudentExtension


# 本地导入
from backend.models.student_extension import StudentExtension  # 必须添加
from backend.models.student import Student
from backend.api.analysis_api import aggregate_query_data
from backend.config.config_manager import ConfigManager
from backend.services.analysis_display_service import AnalysisDisplayService
from backend.services.cache_manager import cache_manager
from flask_jwt_extended import jwt_required, get_jwt_identity

chart_api = Blueprint("chart_api", __name__)

# 初始化配置管理器（遵循三层架构）
config_manager = ConfigManager()

# 初始化显示格式化服务
display_service = AnalysisDisplayService(config_manager)

# 🔧 统一配置：复用REPORT_ROLE_FIELD_CONFIG，避免重复配置
# 通过角色化字段配置获取统计指标字段，确保字段定义的一致性
REPORT_ROLE_CONFIG = config_manager.get_report_role_field_config()
METRIC_FIELDS = REPORT_ROLE_CONFIG.get("metric_fields", {})
GROUP_FIELDS = REPORT_ROLE_CONFIG.get("group_fields", {})
FILTER_FIELDS = REPORT_ROLE_CONFIG.get("filter_fields", {})

# 🔧 保留必要的独立配置（这些配置在REPORT_ROLE_FIELD_CONFIG中没有对应项）
FIXED_METRICS = config_manager.get_config("FIXED_METRICS", [])
CHART_COLORS = config_manager.get_config("CHART_COLORS", {})
FIELD_DISPLAY_MAPPING = config_manager.get_config("FIELD_DISPLAY_MAPPING", {})
# 🔧 修复：统一使用FIELD_DISPLAY_MAPPING，删除FIELD_LABEL_MAPPING别名
FIELD_DISPLAY_MAPPING = config_manager.get_config("FIELD_DISPLAY_MAPPING", {})
complete_fields = config_manager.get_config("COMPLETE_FIELDS", [])


def validate_chart_parameters(query_mode: str, template: str, chart_type: str) -> tuple[bool, str]:
    """
    验证图表请求参数的完整性和有效性
    
    Args:
        query_mode: 查询模式 ("template" 或 "custom")
        template: 模板名称 (仅在template模式下使用)
        chart_type: 图表类型 ("bar", "pie", "line")
    
    Returns:
        tuple: (是否有效, 错误信息)
    """
    # 验证查询模式
    if query_mode not in ["template", "custom"]:
        return False, f"无效的查询模式: {query_mode}，支持的模式: template, custom"
    
    # 验证图表类型
    valid_chart_types = ["bar", "pie", "line"]
    if chart_type not in valid_chart_types:
        return False, f"无效的图表类型: {chart_type}，支持的图表类型: {', '.join(valid_chart_types)}"
    
    # 模板模式下的模板验证
    if query_mode == "template" and not template:
        return False, "模板模式下必须指定模板名称"
    
    if query_mode == "template" and template not in ["template1", "template2"]:
        return False, f"无效的模板名称: {template}，支持的模板: template1, template2"
    
    return True, ""


def log_chart_request_error(error_msg: str, params: dict, exc: Exception = None):
    """
    记录图表请求错误的详细信息
    
    Args:
        error_msg: 错误消息
        params: 请求参数
        exc: 异常对象（可选）
    """
    error_details = {
        "error_type": "chart_request_error",
        "error_message": error_msg,
        "request_params": params,
        "timestamp": datetime.datetime.now().isoformat()
    }
    
    if exc:
        error_details["exception"] = str(exc)
        error_details["traceback"] = traceback.format_exc()
    
    current_app.logger.error("图表请求错误: %s", json.dumps(error_details, ensure_ascii=False))


'''
图表数据接口：
根据请求参数调用统一的聚合查询函数 aggregate_query_data，
获取分组统计数据，并将数据转换为 Chart.js 所需的格式（包含 labels 与多个 datasets）。
支持固定模板与自定义查询两种模式，确保 report 页面和 chart 页面数据一致。
本接口会根据返回的聚合数据中每一行结构进行解析：
  假定每行数据结构为：
    [分组值, 指标1_count, 指标1_ratio, 指标2_count, 指标2_ratio, ..., 总记录数]
对于固定模板（如 template2 按性别），动态指标使用 FIXED_METRICS 中的 distinct_vals，
例如：["临床前期近视", "轻度近视", "中度近视"]。图表将为每个指标生成一条 dataset，
并在 tooltip 中显示百分比信息（前端可在 tooltip 回调中计算）。
'''

def _build_chart_internal(query_mode: str, template: str, stat_time: str, chart_type: str, advanced_conditions: str, request_params: dict):
    """内部复用函数：根据参数生成图表数据 JSON 响应元数据 (不 jsonify)。抛出的异常由调用方捕获。"""
    # 3. 模板模式处理
    if query_mode == "template":
        if not template:
            return {
                "labels": [],
                "datasets": [],
                "chart_type": chart_type,
                "error": "模板模式下必须指定模板名称"
            }, 400
        # 模板模式下仅保留必要参数（此处逻辑仅作语义说明，不改 request.args 以避免副作用）

    # 4. 统计时间
    data_year = stat_time or None
    current_app.logger.debug(f"[CHART] data_year={data_year}")

    # 5. 聚合
    try:
        aggregated_data = aggregate_query_data(
            None, query_mode, template, data_year, advanced_conditions
        )
    except Exception as agg_error:
        error_msg = f"数据聚合失败: {agg_error}"
        log_chart_request_error(error_msg, request_params, agg_error)
        return {"labels": [], "datasets": [], "chart_type": chart_type, "error": error_msg}, 500

    if not aggregated_data:
        return {"labels": [], "datasets": [], "chart_type": chart_type, "error": "未找到符合条件的数据"}, 200

    data_rows = aggregated_data.get("data_rows", [])
    dynamic_metrics = aggregated_data.get("dynamic_metrics", [])
    if not data_rows:
        return {"labels": [], "datasets": [], "chart_type": chart_type, "message": "未找到符合条件的数据"}, 200

    try:
        chart_data = display_service.build_chart_data(data_rows, dynamic_metrics, chart_type)
        
        # ========== 添加返回报表页面的缓存键 ==========
        try:
            current_user_id = get_jwt_identity()
            # 为返回报表页面缓存数据
            return_cache_key = cache_manager.store_data(
                user_id=current_user_id,
                aggregated_data=aggregated_data,
                source_type='chart'
            )
            
            if return_cache_key:
                chart_data['return_cache_key'] = return_cache_key
        except Exception as cache_error:
            current_app.logger.warning(f"图表缓存操作失败: {cache_error}")
        
    except Exception as format_error:
        error_msg = f"图表数据格式化失败: {format_error}"
        log_chart_request_error(error_msg, request_params, format_error)
        return {"labels": [], "datasets": [], "chart_type": chart_type, "error": error_msg}, 500
    return chart_data, 200

@chart_api.route("/api/chart", methods=["GET"])
@jwt_required()
def analysis_chart():
    """
    图表数据生成接口
    
    功能说明:
        根据查询参数生成图表数据，支持固定模板和自定义查询两种模式。
        支持从缓存获取数据或重新查询数据。
        返回Chart.js格式的数据，包含labels和datasets。
    
    请求参数:
        cache_key: 缓存键 (可选，优先使用缓存数据)
        query_mode: 查询模式 ("template" | "custom")
        template: 模板名称 ("template1" | "template2") - 仅在template模式下使用
        stat_time: 统计时间 (年份字符串)
        chart_type: 图表类型 ("bar" | "pie" | "line")
        advanced_conditions: 高级查询条件 (JSON字符串)
    
    返回格式:
        {
            "labels": [分组标签列表],
            "datasets": [数据集列表],
            "chart_type": "图表类型",
            "return_cache_key": "返回报表用的缓存键" (可选),
            "error": "错误信息" (仅在出错时)
        }
    
    错误处理:
        - 参数验证失败: 返回400状态码和详细错误信息
        - 数据聚合失败: 返回500状态码和错误详情
        - 数据转换失败: 返回500状态码和转换错误信息
    """
    # 1. 优先检查缓存数据
    cache_key = request.args.get('cache_key')
    
    if cache_key:
        try:
            # 使用当前登录用户
            current_user_id = get_jwt_identity()
            
            # 从缓存获取聚合数据
            aggregated_data = cache_manager.get_and_refresh(cache_key, current_user_id)
            
            if aggregated_data:
                # 获取图表类型
                chart_type = request.args.get("chart_type", "bar").strip()
                if chart_type not in ["bar", "line", "pie"]:
                    chart_type = "bar"
                
                # 提取数据
                data_rows = aggregated_data.get("data_rows", [])
                dynamic_metrics = aggregated_data.get("dynamic_metrics", [])
                
                if data_rows:
                    # 为返回报表页面缓存数据
                    return_cache_key = cache_manager.store_data(
                        user_id=current_user_id,
                        aggregated_data=aggregated_data,
                        source_type='chart'
                    )
                    
                    # 格式化图表数据
                    chart_data = display_service.build_chart_data(data_rows, dynamic_metrics, chart_type)
                    
                    # 添加返回缓存键
                    if return_cache_key:
                        chart_data['return_cache_key'] = return_cache_key
                    
                    return jsonify(chart_data), 200
                else:
                    pass  # 降级到查询模式
            else:
                pass  # 降级到查询模式
                
        except Exception as cache_error:
            current_app.logger.warning(f"缓存处理失败: {cache_error}")

    # 2. 降级到正常查询流程
    try:
        query_mode = request.args.get("query_mode", "template").strip()
        template = request.args.get("template", "").strip()
        stat_time = request.args.get("stat_time", "").strip()
        chart_type = request.args.get("chart_type", "bar").strip()
        advanced_conditions = request.args.get("advanced_conditions", "").strip()
        
        # 记录请求参数用于调试
        request_params = {
            "query_mode": query_mode,
            "template": template,
            "stat_time": stat_time,
            "chart_type": chart_type,
            "advanced_conditions": advanced_conditions
        }
        
        current_app.logger.debug(
            "图表请求参数 - query_mode: %s, template: %s, stat_time: %s, chart_type: %s",
            query_mode, template, stat_time, chart_type
        )
        
    except Exception as param_error:
        error_msg = f"参数获取失败: {str(param_error)}"
        log_chart_request_error(error_msg, {}, param_error)
        return jsonify({
            "labels": [],
            "datasets": [],
            "chart_type": "bar",
            "error": error_msg
        }), 400

    # 2. 参数验证
    is_valid, validation_error = validate_chart_parameters(query_mode, template, chart_type)
    if not is_valid:
        log_chart_request_error(validation_error, request_params)
        return jsonify({
            "labels": [],
            "datasets": [],
            "chart_type": chart_type,
            "error": validation_error
        }), 400

    try:
        chart_data, status = _build_chart_internal(query_mode, template, stat_time, chart_type, advanced_conditions, request_params)
        return jsonify(chart_data), status
    except Exception as exc:
        error_msg = f"图表生成过程中发生未知错误: {exc}"
        log_chart_request_error(error_msg, request_params, exc)
        return jsonify({"labels": [], "datasets": [], "chart_type": chart_type, "error": error_msg}), 500

@chart_api.route("/api/chart/analysis", methods=["POST"])
@jwt_required()
def analysis_chart_post():
    """兼容前端 /api/chart/analysis POST 请求的图表接口 (双协议兼容)。

    支持两类请求体：
    1. 新结构（聚合统计）：{"query_mode": "template"|"custom", "template": "template1", ...}
       -> 返回与 GET /api/chart 相同的结构（labels/datasets/chart_type）。
    2. 旧欢迎页结构：{"chart_type": "line", "group_by": "month", "metrics": [...], "filters": {...}}
       -> 返回 {"success": True, "data": {labels:[], datasets:[]}} 以匹配 welcome.js 逻辑。
    """
    try:
        body = request.get_json(silent=True) or {}

        # --- 分支判定：是否为旧结构请求（存在 group_by 或 metrics 字段） ---
        if 'group_by' in body or 'metrics' in body:
            # 旧协议：为了不阻塞首页展示，若无真实数据则返回演示数据即可。
            chart_type = (body.get('chart_type') or 'line').strip()
            # 简单生成 6 个月演示数据（后续可接入真实逻辑）
            demo_labels = body.get('labels') or ['1月','2月','3月','4月','5月','6月']
            demo_dataset = {
                'label': '示例',
                'data': body.get('demo_data') or [4.8,4.9,5.0,4.9,5.1,5.2],
                'borderColor': '#667eea',
                'backgroundColor': 'rgba(102,126,234,0.1)',
                'borderWidth': 3,
                'fill': True,
                'tension': 0.4
            }
            return jsonify({
                'success': True,
                'data': {
                    'labels': demo_labels,
                    'datasets': [demo_dataset],
                    'chart_type': chart_type
                },
                'message': 'legacy chart demo'
            })

        # --- 新协议处理 ---
        query_mode = (body.get("query_mode") or "template").strip()
        template = (body.get("template") or "").strip()
        stat_time = (body.get("stat_time") or body.get("data_year") or "").strip()
        chart_type = (body.get("chart_type") or "bar").strip()
        advanced_conditions = body.get("advanced_conditions") or ""
        request_params = {
            "query_mode": query_mode,
            "template": template,
            "stat_time": stat_time,
            "chart_type": chart_type,
            "advanced_conditions": advanced_conditions
        }
        is_valid, validation_error = validate_chart_parameters(query_mode, template, chart_type)
        if not is_valid:
            log_chart_request_error(validation_error, request_params)
            # 新协议返回旧结构（不返回 success 字段）保持与 GET /api/chart 一致
            return jsonify({"labels": [], "datasets": [], "chart_type": chart_type, "error": validation_error}), 400
        chart_data, status = _build_chart_internal(query_mode, template, stat_time, chart_type, advanced_conditions, request_params)
        return jsonify(chart_data), status
    except Exception as exc:
        error_msg = f"图表(analysis)处理失败: {exc}"
        log_chart_request_error(error_msg, {}, exc)
        return jsonify({"labels": [], "datasets": [], "chart_type": "bar", "error": error_msg}), 500

@chart_api.route("/api/health/db", methods=["GET"])
def db_health():
    """数据库健康检查：尝试一次轻量连接并返回统计(错误计数需在 app.config 中维护)。"""
    from flask import current_app
    from sqlalchemy import text
    start = datetime.datetime.now()
    ok = True
    version = None
    error = None
    try:
        with db.engine.connect() as conn:
            if db.engine.dialect.name == 'mysql':
                version = conn.execute(text('SELECT VERSION()')).scalar()
            else:
                version = conn.execute(text('SELECT 1')).scalar()
    except Exception as e:  # 不抛出到全局，直接返回
        ok = False
        error = str(e)
    duration_ms = int((datetime.datetime.now() - start).total_seconds() * 1000)
    return jsonify({
        'success': ok,
        'engine': db.engine.dialect.name,
        'version': version,
        'duration_ms': duration_ms,
        'db_error_count': current_app.config.get('DB_ERROR_COUNT', 0),
        'error': error
    }), (200 if ok else 503)


'''
def validate_chart_data(func):
    """确保图表数据与报表的合计行一致"""
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)

        # 仅在生产环境校验
        if current_app.config.get("ENV") == "production":
            from backend.utils import compare_totals  # 假设存在的校验工具
            if not compare_totals(result["datasets"][-1]["data"], kwargs.get("expected_totals")):
                current_app.logger.warning("图表合计行校验失败")

        return result
    return wrapper
# 插入代码结束 - 原有代码保留
'''